const Scope = require('./scope');
const walk = require('./walk');

/**
 * 找出当前模块使用到了哪些变量 (深度优先遍历)
 * 还要知道哪些变量是当前模块声明的, 哪些变量是导入别的模块的变量, 这会影响打包的方式
 * 当前模块声明的从当前模块取, 引用的别的模块的变量要从变的模块里取
 *
 * @param ast
 * @param magicStringSource
 * @param module
 */
function analyse(ast, magicStringSource, module) {
  //模块内的一个全局作用域
  let scope = new Scope();

  //遍历当前模块的所有顶级节点
  //构建作用域链
  //填充 _defines
  ast.body.forEach(statement => {
    //给作用域添加变量
    function addToScope(declaration, isBlockDeclaration = false) {
      var name = declaration.id.name;
      scope.add(name,isBlockDeclaration);
      if (!scope.parent || (!isBlockDeclaration/* todo 有可能是在 `fn(){var x= 333}` 就不是全局的 */)) { //当前是全局作用域
        statement._defines[name] = true;
      }
    }

    Object.defineProperties(statement, {
      _source: {value: magicStringSource.snip(statement.start, statement.end)},
      _defines: {value: {}}, //该条语句定义了哪些全局变量
      _dependsOn: {value: {}}, //该条语句依赖的标识符（变量）
      _included: {value: false, writable: true}, //此语句是否已经被包含到打包结果中了 //导出的一条语句 可能被多个模块引用
      _modifies: {value:{}}, //修改的语句
      _module:{value: module}
    });

    //构建作用域链
    //填充 _defines
    walk(statement, {
      enter(node) {
        let newScope;
        switch (node.type) {
          //todo 箭头函数、 块级作用域
          case 'FunctionDeclaration':
            const params = node.params.map(x => x.name);
            addToScope(node);
            //遇到一个函数 会新开启一个子作用域
            newScope = new Scope({
              parent: scope,
              params,
              block: false
            });
            break;
          case 'BlockStatement': //见02.mini-rollup/readme-assets/03BlockStatement.png
            newScope = new Scope({
              parent: scope,
              block: true //这就是一个块级作用域了
            })
            break;
          case 'VariableDeclaration':
            node.declarations.forEach((variableDeclarator) => { //见 02.mini-rollup/readme-assets/04VariableDeclaration.png
              if(node.kind === 'let' || node.kind === 'const'){
                addToScope(variableDeclarator, true); //这是一个块级
              }else {
                addToScope(variableDeclarator);
              }
            });
            break;
        }
        if (newScope) {
          //如果此节点生成了一个新的作用域, 那么会在该节点上方一个_scope, 指向新作用域
          //这样这函数节点的._scope 就是 它的孩子函数节点 的查找作用域 (即b的查找作用域 就是 a._scope
          /*
            function a(){
              function b(){}
            }
          */
          Object.defineProperty(node, '_scope', {value: newScope});
          scope = newScope // walk是深度优先, 这样, 后面的孩子们调用addToScope, 就是往这个newScope, 即它的父函数作用域中push
        }
      },
      leave(node) {
        if(node._scope) scope = scope.parent;
      }
    });
  });

  //填充 _dependsOn (即找出模块的外部依赖的变量)
  ast.body.forEach(statement => {
    function checkForReads(node){
      if (node.type === 'Identifier') {
        statement._dependsOn[node.name] = true; //包含当前声明的 和 外部依赖过来的 全部变量(标识符依赖)
      }
    }
    function checkForWrites(node){
      //查看02.png, `age++` 和 `age=12` 这俩语句都会把自个的 _dependsOn[age] = true
      if (node.type === 'AssignmentExpression') {
        addNode(node.left, true);
      }else if (node.type === 'UpdateExpression') {
        addNode(node.argument, true);
      }

      function addNode(node){
        statement._modifies[node.name] = true;
      }
    }

    walk(statement, {
      enter(node){
        if(node._scope) scope = node._scope;
        /*if (node.type === 'Identifier') {
          //从当前的作用域出发向上递归, 找这个变量在哪个作用域中定义
          const definingScope = scope.findDefiningScope(node.name); //顺着作用域链往上查找
          if (!definingScope) {
            statement._dependsOn[node.name] = true; //作用域链上找不到, 是一个外部依赖的变量
          }
        }*/
        checkForReads(node); //查看读取的标识符

        checkForWrites(node); //查看修改哪些标识符
      },
      leave(node){
        if(node._scope) scope = scope.parent;
      }
    });
  });

}

module.exports = analyse;
